//  (C) Copyright John Maddock 2016.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifdef _MSC_VER
#pragma warning(disable : 4127)    // conditional expression is constant
#endif

#if !defined(TEST_MPF_50) && !defined(TEST_MPF) && !defined(TEST_BACKEND) && !defined(TEST_CPP_DEC_FLOAT) && \
    !defined(TEST_MPFR) && !defined(TEST_MPFR_50) && !defined(TEST_MPFI_50) && !defined(TEST_FLOAT128) &&    \
    !defined(TEST_CPP_BIN_FLOAT) && !defined(TEST_CPP_DEC_FLOAT_2) && !defined(TEST_CPP_DEC_FLOAT_3) &&      \
    !defined(TEST_CPP_DEC_FLOAT_4) && !defined(TEST_CPP_DEC_FLOAT_5) && !defined(TEST_CPP_DEC_FLOAT_6) &&    \
    !defined(TEST_CPP_BIN_FLOAT_2) && !defined(TEST_CPP_BIN_FLOAT_3)
#define TEST_MPF_50
#define TEST_MPFR_50
#define TEST_MPFI_50
#define TEST_CPP_DEC_FLOAT
#define TEST_CPP_DEC_FLOAT_2
#define TEST_CPP_DEC_FLOAT_3
#define TEST_CPP_DEC_FLOAT_4
#define TEST_CPP_DEC_FLOAT_5
#define TEST_CPP_DEC_FLOAT_6
#define TEST_FLOAT128
#define TEST_CPP_BIN_FLOAT
#define TEST_CPP_BIN_FLOAT_2
#define TEST_CPP_BIN_FLOAT_3

#ifdef _MSC_VER
#pragma message("CAUTION!!: No backend type specified so testing everything.... this will take some time!!")
#endif
#ifdef __GNUC__
#pragma warning "CAUTION!!: No backend type specified so testing everything.... this will take some time!!"
#endif

#endif

#if defined(TEST_MPF_50)
#include <nil/crypto3/multiprecision/gmp.hpp>
#endif
#ifdef TEST_MPFR_50
#include <nil/crypto3/multiprecision/mpfr.hpp>
#endif
#ifdef TEST_MPFI_50
#include <nil/crypto3/multiprecision/mpfi.hpp>
#endif
#if defined(TEST_CPP_DEC_FLOAT) || defined(TEST_CPP_DEC_FLOAT_2) || defined(TEST_CPP_DEC_FLOAT_3) || \
    defined(TEST_CPP_DEC_FLOAT_4) || defined(TEST_CPP_DEC_FLOAT_5) || defined(TEST_CPP_DEC_FLOAT_6)
#include <nil/crypto3/multiprecision/cpp_dec_float.hpp>
#endif
#if defined(TEST_CPP_BIN_FLOAT) || defined(TEST_CPP_BIN_FLOAT_2) || defined(TEST_CPP_BIN_FLOAT_3)
#include <nil/crypto3/multiprecision/cpp_bin_float.hpp>
#include <nil/crypto3/multiprecision/debug_adaptor.hpp>
#endif
#ifdef TEST_FLOAT128
#include <nil/crypto3/multiprecision/float128.hpp>
#endif

#include <boost/math/constants/constants.hpp>
#include <boost/math/special_functions/gamma.hpp>
#include "test.hpp"

#ifdef signbit
#undef signbit
#endif
#ifdef sign
#undef sign
#endif
#ifdef changesign
#undef changesign
#endif
#ifdef copysign
#undef copysign
#endif
#ifdef fpclassify
#undef fpclassify
#endif
#ifdef isinf
#undef isinf
#endif
#ifdef isnan
#undef isnan
#endif
#ifdef isnormal
#undef isnormal
#endif

#ifdef MPFR_VERSION_MAJOR
#define BOOST_MPFR_VERSION MPFR_VERSION_MAJOR * 10000 + MPFR_VERSION_MINOR * 100 + MPFR_VERSION_PATCHLEVEL
#endif

template<class T, class U>
void test_less(T a, U b) {
    BOOST_CHECK(a < b);
    BOOST_CHECK(a <= b);
    BOOST_CHECK(!(a > b));
    BOOST_CHECK(!(a >= b));
    BOOST_CHECK(!(a == b));
    BOOST_CHECK((a != b));

    BOOST_CHECK(b > a);
    BOOST_CHECK(b >= a);
    BOOST_CHECK(!(b < a));
    BOOST_CHECK(!(b <= a));
    BOOST_CHECK(!(b == a));
    BOOST_CHECK((b != a));

    BOOST_CHECK(isless(a, b));
    BOOST_CHECK(islessequal(a, b));
    BOOST_CHECK(!isgreater(a, b));
    BOOST_CHECK(!isgreaterequal(a, b));
    BOOST_CHECK(islessgreater(a, b));

    BOOST_CHECK(!isless(b, a));
    BOOST_CHECK(!islessequal(b, a));
    BOOST_CHECK(isgreater(b, a));
    BOOST_CHECK(isgreaterequal(b, a));
    BOOST_CHECK(islessgreater(b, a));
}
template<class T, class U>
void test_equal(T a, U b) {
    BOOST_CHECK(!(a < b));
    BOOST_CHECK(a <= b);
    BOOST_CHECK(!(a > b));
    BOOST_CHECK((a >= b));
    BOOST_CHECK((a == b));
    BOOST_CHECK(!(a != b));

    BOOST_CHECK(!(b > a));
    BOOST_CHECK(b >= a);
    BOOST_CHECK(!(b < a));
    BOOST_CHECK((b <= a));
    BOOST_CHECK((b == a));
    BOOST_CHECK(!(b != a));

    BOOST_CHECK(!isless(a, b));
    BOOST_CHECK(islessequal(a, b));
    BOOST_CHECK(!isgreater(a, b));
    BOOST_CHECK(isgreaterequal(a, b));
    BOOST_CHECK(!islessgreater(a, b));

    BOOST_CHECK(!isless(b, a));
    BOOST_CHECK(islessequal(b, a));
    BOOST_CHECK(!isgreater(b, a));
    BOOST_CHECK(isgreaterequal(b, a));
    BOOST_CHECK(!islessgreater(b, a));
}
template<class T, class U>
void test_unordered(T a, U b) {
    BOOST_CHECK(!(a < b));
    BOOST_CHECK(!(a <= b));
    BOOST_CHECK(!(a > b));
    BOOST_CHECK(!(a >= b));
    BOOST_CHECK(!(a == b));
    BOOST_CHECK((a != b));

    BOOST_CHECK(!(b > a));
    BOOST_CHECK(!(b >= a));
    BOOST_CHECK(!(b < a));
    BOOST_CHECK(!(b <= a));
    BOOST_CHECK(!(b == a));
    BOOST_CHECK((b != a));

    BOOST_CHECK(!isless(a, b));
    BOOST_CHECK(!islessequal(a, b));
    BOOST_CHECK(!isgreater(a, b));
    BOOST_CHECK(!isgreaterequal(a, b));
    BOOST_CHECK(!islessgreater(a, b));

    BOOST_CHECK(!isless(b, a));
    BOOST_CHECK(!islessequal(b, a));
    BOOST_CHECK(!isgreater(b, a));
    BOOST_CHECK(!isgreaterequal(b, a));
    BOOST_CHECK(!islessgreater(b, a));
}

template<class T>
void test() {
    //
    // Basic sanity checks for C99 functions which are just imported versions
    // from Boost.Math.  These should still be found via ADL so no using declarations here...
    //
    T val = 2;
    BOOST_CHECK(signbit(val) == 0);
    BOOST_CHECK(signbit(val + 2) == 0);
    val = -val;
    BOOST_CHECK(signbit(val));
    BOOST_CHECK(signbit(val * 2));

    val = 2;
    BOOST_CHECK_EQUAL(sign(val), 1);
    BOOST_CHECK_EQUAL(sign(val + 2), 1);
    val = -val;
    BOOST_CHECK_EQUAL(sign(val), -1);
    BOOST_CHECK_EQUAL(sign(val * 2), -1);
    val = 0;
    BOOST_CHECK_EQUAL(sign(val), 0);
    BOOST_CHECK_EQUAL(sign(val * 2), 0);

    val = 2;
    BOOST_CHECK_EQUAL(changesign(val), -2);
    BOOST_CHECK_EQUAL(changesign(val * 2), -4);
    val = -2;
    BOOST_CHECK_EQUAL(changesign(val), 2);
    BOOST_CHECK_EQUAL(changesign(val * 2), 4);
    val = 0;
    BOOST_CHECK_EQUAL(changesign(val), 0);
    BOOST_CHECK_EQUAL(changesign(val * 2), 0);
    // Things involving signed zero, need to detect it first:
    T neg_zero_test = -(std::numeric_limits<T>::min)();
    neg_zero_test /= (std::numeric_limits<T>::max)();
    T one(1);
    bool test_signed_zero = !nil::crypto3::multiprecision::is_interval_number<T>::value &&
                            std::numeric_limits<T>::has_infinity && (one / neg_zero_test < 0);
    if (test_signed_zero) {
        BOOST_CHECK(signbit(changesign(val)));
        BOOST_CHECK(signbit(changesign(val * 2)));
    }

    T s = 2;
    val = 3;
    BOOST_CHECK_EQUAL(copysign(val, s), 3);
    BOOST_CHECK_EQUAL(copysign(val, s * -2), -3);
    BOOST_CHECK_EQUAL(copysign(-2 * val, s), 6);
    BOOST_CHECK_EQUAL(copysign(-2 * val, 2 * s), 6);
    s = -2;
    BOOST_CHECK_EQUAL(copysign(val, s), -3);
    BOOST_CHECK_EQUAL(copysign(val, s * -2), 3);
    BOOST_CHECK_EQUAL(copysign(-2 * val, s), -6);
    BOOST_CHECK_EQUAL(copysign(-2 * val, 2 * s), -6);
    val = -3;
    BOOST_CHECK_EQUAL(copysign(val, s), -3);
    BOOST_CHECK_EQUAL(copysign(val, s * -2), 3);
    BOOST_CHECK_EQUAL(copysign(-2 * val, s), -6);
    BOOST_CHECK_EQUAL(copysign(-2 * val, 2 * s), -6);
    s = 0;
    BOOST_CHECK_EQUAL(copysign(val, s), 3);
    // Things involving signed zero, need to detect it first:
    if (test_signed_zero) {
        BOOST_CHECK_EQUAL(copysign(val, s * -2), -3);
    }
    BOOST_CHECK_EQUAL(copysign(-2 * val, s), 6);
    BOOST_CHECK_EQUAL(copysign(-2 * val, 2 * s), 6);
    // Things involving signed zero, need to detect it first:
    if (test_signed_zero) {
        s = changesign(s);
        if (signbit(s)) {
            BOOST_CHECK_EQUAL(copysign(val, s), -3);
            BOOST_CHECK_EQUAL(copysign(val, s * -2), 3);
            BOOST_CHECK_EQUAL(copysign(-2 * val, s), -6);
            BOOST_CHECK_EQUAL(copysign(-2 * val, 2 * s), -6);
        }
    }

    val = 3;
    BOOST_CHECK_EQUAL(fpclassify(val), FP_NORMAL);
    BOOST_CHECK_EQUAL(fpclassify(val * 3), FP_NORMAL);
    BOOST_CHECK(!isinf(val));
    BOOST_CHECK(!isinf(val + 2));
    BOOST_CHECK(!isnan(val));
    BOOST_CHECK(!isnan(val + 2));
    BOOST_CHECK(isnormal(val));
    BOOST_CHECK(isnormal(val + 2));
    val = -3;
    BOOST_CHECK_EQUAL(fpclassify(val), FP_NORMAL);
    BOOST_CHECK_EQUAL(fpclassify(val * 3), FP_NORMAL);
    BOOST_CHECK(!isinf(val));
    BOOST_CHECK(!isinf(val + 2));
    BOOST_CHECK(!isnan(val));
    BOOST_CHECK(!isnan(val + 2));
    BOOST_CHECK(isnormal(val));
    BOOST_CHECK(isnormal(val + 2));
    val = 0;
    BOOST_CHECK_EQUAL(fpclassify(val), FP_ZERO);
    BOOST_CHECK_EQUAL(fpclassify(val * 3), FP_ZERO);
    BOOST_CHECK(!isinf(val));
    BOOST_CHECK(!isinf(val + 2));
    BOOST_CHECK(!isnan(val));
    BOOST_CHECK(!isnan(val + 2));
    BOOST_CHECK(!isnormal(val));
    BOOST_CHECK(!isnormal(val * 2));
    BOOST_CHECK(!isnormal(val * -2));
    if (std::numeric_limits<T>::has_infinity) {
        val = std::numeric_limits<T>::infinity();
        BOOST_CHECK_EQUAL(fpclassify(val), FP_INFINITE);
        BOOST_CHECK_EQUAL(fpclassify(val * 3), FP_INFINITE);
        BOOST_CHECK(isinf(val));
        BOOST_CHECK(isinf(val + 2));
        BOOST_CHECK(!isnan(val));
        BOOST_CHECK(!isnan(val + 2));
        BOOST_CHECK(!isnormal(val));
        BOOST_CHECK(!isnormal(val + 2));
        val = -val;
        BOOST_CHECK_EQUAL(fpclassify(val), FP_INFINITE);
        BOOST_CHECK_EQUAL(fpclassify(val * 3), FP_INFINITE);
        BOOST_CHECK(isinf(val));
        BOOST_CHECK(isinf(val + 2));
        BOOST_CHECK(!isnan(val));
        BOOST_CHECK(!isnan(val + 2));
        BOOST_CHECK(!isnormal(val));
        BOOST_CHECK(!isnormal(val + 2));
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        val = std::numeric_limits<T>::quiet_NaN();
        BOOST_CHECK_EQUAL(fpclassify(val), FP_NAN);
        BOOST_CHECK_EQUAL(fpclassify(val * 3), FP_NAN);
        BOOST_CHECK(!isinf(val));
        BOOST_CHECK(!isinf(val + 2));
        BOOST_CHECK(isnan(val));
        BOOST_CHECK(isnan(val + 2));
        BOOST_CHECK(!isnormal(val));
        BOOST_CHECK(!isnormal(val + 2));
    }
    s = 8 * std::numeric_limits<T>::epsilon();
    val = 2.5;
    BOOST_CHECK_CLOSE_FRACTION(asinh(val),
                               T("1."
                                 "6472311463710957106248586104436196635044144301932365282203100930843983757633104078778"
                                 "4202550694249077770061320755164847787553605959131722990938295229503978956996195405235"
                                 "79875476513967578478619028438291006578604823887119907434"),
                               s);
    BOOST_CHECK_CLOSE_FRACTION(asinh(val + T(0)),
                               T("1."
                                 "6472311463710957106248586104436196635044144301932365282203100930843983757633104078778"
                                 "4202550694249077770061320755164847787553605959131722990938295229503978956996195405235"
                                 "79875476513967578478619028438291006578604823887119907434"),
                               s);
    BOOST_CHECK_CLOSE_FRACTION(acosh(val),
                               T("1."
                                 "5667992369724110786640568625804834938620823510926588639329459980122148134693922696279"
                                 "9684996222011410510391840509363110664535653863932403565623743024178433194802232118576"
                                 "15778787272615171906055455922537080327062362258846337050"),
                               s);
    BOOST_CHECK_CLOSE_FRACTION(acosh(val + T(0)),
                               T("1."
                                 "5667992369724110786640568625804834938620823510926588639329459980122148134693922696279"
                                 "9684996222011410510391840509363110664535653863932403565623743024178433194802232118576"
                                 "15778787272615171906055455922537080327062362258846337050"),
                               s);
    val = 0.5;
    BOOST_CHECK_CLOSE_FRACTION(atanh(val),
                               T("0."
                                 "5493061443340548456976226184612628523237452789113747258673471668187471466093044834368"
                                 "0787740686604439398501453297893287118400211296525991052640093538363870530158138459169"
                                 "06835896868494221804799518712851583979557605727959588753"),
                               s);
    BOOST_CHECK_CLOSE_FRACTION(atanh(val + T(0)),
                               T("0."
                                 "5493061443340548456976226184612628523237452789113747258673471668187471466093044834368"
                                 "0787740686604439398501453297893287118400211296525991052640093538363870530158138459169"
                                 "06835896868494221804799518712851583979557605727959588753"),
                               s);
    val = 55.25;
    BOOST_CHECK_CLOSE_FRACTION(cbrt(val),
                               T("3."
                                 "8087058015466360309383876359583281382991983919300128125378938779672144843676192684301"
                                 "1684796572794981207674247240249653198692487974232760640156433614261895764156709178183"
                                 "13417529572608229017809069355688606687557031643655896118"),
                               s);
    BOOST_CHECK_CLOSE_FRACTION(cbrt(val + T(0)),
                               T("3."
                                 "8087058015466360309383876359583281382991983919300128125378938779672144843676192684301"
                                 "1684796572794981207674247240249653198692487974232760640156433614261895764156709178183"
                                 "13417529572608229017809069355688606687557031643655896118"),
                               s);
    if (!nil::crypto3::multiprecision::is_interval_number<T>::value) {
        val = 2.75;
        BOOST_CHECK_CLOSE_FRACTION(erf(val),
                                   T("0."
                                     "999899378077880363163095608024913043234935262142264065516109579465452642202590896"
                                     "144732829668105689297521434477930073462025539168271351926504849619903496370697642"
                                     "0982849598189071465666866369396765001072187538732800143945532487"),
                                   s);
        BOOST_CHECK_CLOSE_FRACTION(erf(val + T(0)),
                                   T("0."
                                     "999899378077880363163095608024913043234935262142264065516109579465452642202590896"
                                     "144732829668105689297521434477930073462025539168271351926504849619903496370697642"
                                     "0982849598189071465666866369396765001072187538732800143945532487"),
                                   s);
        BOOST_CHECK_CLOSE_FRACTION(erfc(val),
                                   T("0."
                                     "000100621922119636836904391975086956765064737857735934483890420534547357797409103"
                                     "855267170331894310702478565522069926537974460831728648073495150380096503629302357"
                                     "9017150401810928534333133630603234998927812461267199856054467512"),
                                   s);
        BOOST_CHECK_CLOSE_FRACTION(erfc(val + T(0)),
                                   T("0."
                                     "000100621922119636836904391975086956765064737857735934483890420534547357797409103"
                                     "855267170331894310702478565522069926537974460831728648073495150380096503629302357"
                                     "9017150401810928534333133630603234998927812461267199856054467512"),
                                   s);
    }
    val = 0.125;
    BOOST_CHECK_CLOSE_FRACTION(expm1(val),
                               T("0."
                                 "1331484530668263168290072278117938725655031317451816259128200360788235778800483865139"
                                 "3999079494172857323152701564730756570482104525847339987855640259169952611627592807003"
                                 "97984729320345630340659469435372721057879969170503978449"),
                               s);
    BOOST_CHECK_CLOSE_FRACTION(expm1(val + T(0)),
                               T("0."
                                 "1331484530668263168290072278117938725655031317451816259128200360788235778800483865139"
                                 "3999079494172857323152701564730756570482104525847339987855640259169952611627592807003"
                                 "97984729320345630340659469435372721057879969170503978449"),
                               s);

    val = 20;
    s = 2;
    BOOST_CHECK_EQUAL(fdim(val, s), 18);
    BOOST_CHECK_EQUAL(fdim(s, val), 0);
    BOOST_CHECK_EQUAL(fdim(val, s * 2), 16);
    BOOST_CHECK_EQUAL(fdim(s * 2, val), 0);
    BOOST_CHECK_EQUAL(fdim(val, 2), 18);
    BOOST_CHECK_EQUAL(fdim(2, val), 0);

    BOOST_CHECK_EQUAL(fmax(val, s), val);
    BOOST_CHECK_EQUAL(fmax(s, val), val);
    BOOST_CHECK_EQUAL(fmax(val * 2, s), val * 2);
    BOOST_CHECK_EQUAL(fmax(val, s * 2), val);
    BOOST_CHECK_EQUAL(fmax(val * 2, s * 2), val * 2);
    BOOST_CHECK_EQUAL(fmin(val, s), s);
    BOOST_CHECK_EQUAL(fmin(s, val), s);
    BOOST_CHECK_EQUAL(fmin(val * 2, s), s);
    BOOST_CHECK_EQUAL(fmin(val, s * 2), s * 2);
    BOOST_CHECK_EQUAL(fmin(val * 2, s * 2), s * 2);

    BOOST_CHECK_EQUAL(fmax(val, 2), val);
    BOOST_CHECK_EQUAL(fmax(val, 2.0), val);
    BOOST_CHECK_EQUAL(fmax(20, s), val);
    BOOST_CHECK_EQUAL(fmax(20.0, s), val);
    BOOST_CHECK_EQUAL(fmin(val, 2), s);
    BOOST_CHECK_EQUAL(fmin(val, 2.0), s);
    BOOST_CHECK_EQUAL(fmin(20, s), s);
    BOOST_CHECK_EQUAL(fmin(20.0, s), s);
    if (std::numeric_limits<T>::has_quiet_NaN) {
        BOOST_CHECK_EQUAL(fmax(val, std::numeric_limits<T>::quiet_NaN()), val);
        BOOST_CHECK_EQUAL(fmax(std::numeric_limits<T>::quiet_NaN(), val), val);
        BOOST_CHECK_EQUAL(fmin(val, std::numeric_limits<T>::quiet_NaN()), val);
        BOOST_CHECK_EQUAL(fmin(std::numeric_limits<T>::quiet_NaN(), val), val);
    }
    if (std::numeric_limits<double>::has_quiet_NaN) {
        BOOST_CHECK_EQUAL(fmax(val, std::numeric_limits<double>::quiet_NaN()), val);
        BOOST_CHECK_EQUAL(fmax(std::numeric_limits<double>::quiet_NaN(), val), val);
        BOOST_CHECK_EQUAL(fmin(val, std::numeric_limits<double>::quiet_NaN()), val);
        BOOST_CHECK_EQUAL(fmin(std::numeric_limits<double>::quiet_NaN(), val), val);
    }

    test_less(s, val);
    test_less(2, val);
    test_less(s, 20);
    test_less(s + 0, val);
    test_less(s, val * 1);
    test_less(s * 1, val * 1);
    test_less(s * 1, 20);
    test_less(s + 2, val * 2);

    test_equal(val, val);
    test_equal(20, val);
    test_equal(val, 20);
    test_equal(val + 0, val);
    test_equal(val, val * 1);
    test_equal(val * 1, val * 1);
    test_equal(val * 1, 20);
    test_equal(val * 20, val * 20);

    if (std::numeric_limits<T>::has_quiet_NaN) {
        s = std::numeric_limits<T>::quiet_NaN();
        test_unordered(s, val);
        test_unordered(s, 20);
        test_unordered(s + 0, val);
        test_unordered(s, val * 1);
        test_unordered(s * 1, val * 1);
        test_unordered(s * 1, 20);
        test_unordered(s + 2, val * 2);
        if (std::numeric_limits<double>::has_quiet_NaN) {
            double n = std::numeric_limits<double>::quiet_NaN();
            test_unordered(n, val);
        }
    }

    T tol = 8 * std::numeric_limits<T>::epsilon();
    s = 2;
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(val, s)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(val, 2)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(val, 2.0)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(20, s)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(20.0, s)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(val * 1, s)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(val * 1, s * 1)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(val * 1, 2)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(val * 1, 2.0)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(20, s * 1)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(hypot(20.0, s * 1)),
                               T("20."
                                 "0997512422417805404385298255191523738900469400527545811456565946569824631039407624723"
                                 "55384907904704732599006530"),
                               tol);

    BOOST_CHECK_CLOSE_FRACTION(lgamma(val),
                               T("39."
                                 "3398841871994940362246523945673810816914572068978531199379699893775725549938744762493"
                                 "40525204204720861169039582"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(lgamma(val + 0),
                               T("39."
                                 "3398841871994940362246523945673810816914572068978531199379699893775725549938744762493"
                                 "40525204204720861169039582"),
                               tol);

    BOOST_CHECK_EQUAL(lrint(val), 20);
    BOOST_CHECK_EQUAL(lrint(val * 2), 40);
    BOOST_CHECK_EQUAL(llrint(val), 20);
    BOOST_CHECK_EQUAL(llrint(val * 2), 40);

    val = 0.125;
    BOOST_CHECK_CLOSE_FRACTION(log1p(val),
                               T("0."
                                 "1177830356563834545387941094705217050684807125647331411073486387948077205281337869296"
                                 "41528638208114949935615070"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(log1p(val + 0),
                               T("0."
                                 "1177830356563834545387941094705217050684807125647331411073486387948077205281337869296"
                                 "41528638208114949935615070"),
                               tol);
    val = 20;
    BOOST_CHECK_CLOSE_FRACTION(T(log2(val)),
                               T("4."
                                 "3219280948873623478703194294893901758648313930245806120547563958159347766086252158501"
                                 "39743359370155099657371710"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(log2(val + 0)),
                               T("4."
                                 "3219280948873623478703194294893901758648313930245806120547563958159347766086252158501"
                                 "39743359370155099657371710"),
                               tol);

    BOOST_CHECK_EQUAL(T(nearbyint(val)), 20);
    BOOST_CHECK_EQUAL(T(nearbyint(val + 0.25)), 20);
    BOOST_CHECK_EQUAL(T(rint(val)), 20);
    BOOST_CHECK_EQUAL(T(rint(val + 0.25)), 20);

    BOOST_CHECK_GT(nextafter(val, T(200)), val);
    BOOST_CHECK_GT(nextafter(val + 0, T(200)), val);
    BOOST_CHECK_GT(nextafter(val + 0, T(200) + 1), val);
    BOOST_CHECK_GT(nextafter(val, T(200) + 1), val);

    BOOST_CHECK_GT(nexttoward(val, T(200)), val);
    BOOST_CHECK_GT(nexttoward(val + 0, T(200)), val);
    BOOST_CHECK_GT(nexttoward(val + 0, T(200) + 1), val);
    BOOST_CHECK_GT(nexttoward(val, T(200) + 1), val);

    val = 21;
    s = 5;
    BOOST_CHECK_EQUAL(T(remainder(val, s)), 1);
    BOOST_CHECK_EQUAL(T(remainder(val, 5)), 1);
    BOOST_CHECK_EQUAL(T(remainder(21, s)), 1);
    BOOST_CHECK_EQUAL(T(remainder(val * 1, s)), 1);
    BOOST_CHECK_EQUAL(T(remainder(val * 1, s * 1)), 1);
    BOOST_CHECK_EQUAL(T(remainder(val, s * 1)), 1);
    BOOST_CHECK_EQUAL(T(remainder(val * 1, 5)), 1);
    BOOST_CHECK_EQUAL(T(remainder(21, s * 1)), 1);
    int i(0);
    BOOST_CHECK_EQUAL(T(remquo(val, s, &i)), 1);
    BOOST_CHECK_EQUAL(i, 4);
    i = 0;
    BOOST_CHECK_EQUAL(T(remquo(val, 5, &i)), 1);
    BOOST_CHECK_EQUAL(i, 4);
    i = 0;
    BOOST_CHECK_EQUAL(T(remquo(21, s, &i)), 1);
    BOOST_CHECK_EQUAL(i, 4);
    i = 0;
    BOOST_CHECK_EQUAL(T(remquo(val * 1, s, &i)), 1);
    BOOST_CHECK_EQUAL(i, 4);
    i = 0;
    BOOST_CHECK_EQUAL(T(remquo(val * 1, s * 1, &i)), 1);
    BOOST_CHECK_EQUAL(i, 4);
    i = 0;
    BOOST_CHECK_EQUAL(T(remquo(val, s * 1, &i)), 1);
    BOOST_CHECK_EQUAL(i, 4);
    i = 0;
    BOOST_CHECK_EQUAL(T(remquo(val * 1, 5, &i)), 1);
    BOOST_CHECK_EQUAL(i, 4);
    i = 0;
    BOOST_CHECK_EQUAL(T(remquo(21, s * 1, &i)), 1);
    BOOST_CHECK_EQUAL(i, 4);
    i = 0;
    val = 5.25;
    tol = 3000;
    BOOST_CHECK_CLOSE_FRACTION(tgamma(val),
                               T("35."
                                 "2116118527996857052252576905312481150263111389084483140868595759012176533131456196236"
                                 "24570033258659272301335544"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(tgamma(val + 1),
                               T("184."
                                 "8609622271983499524326028752890526038881334792693536489560127734813926798940145030240"
                                 "2899267460796117958201160"),
                               tol);

    BOOST_CHECK_CLOSE_FRACTION(T(exp2(val)),
                               T("38."
                                 "0546276800870741349599990579352292893751069588421572166080711910229333832613491158650"
                                 "03025220405558913196632792"),
                               tol);
    BOOST_CHECK_CLOSE_FRACTION(T(exp2(val + 1)),
                               T("76."
                                 "1092553601741482699199981158704585787502139176843144332161423820458667665226982317300"
                                 "06050440811117826393265585"),
                               tol);
    val = 15;
    BOOST_CHECK_CLOSE_FRACTION(T(exp2(val)), T(32768uL), tol);
    BOOST_CHECK_CLOSE_FRACTION(T(exp2(val + 1)), T(65536uL), tol);

    i = fpclassify(val) + isgreaterequal(val, s) + islessequal(val, s) + isnan(val) + isunordered(val, s) +
        isfinite(val) + isinf(val) + islessgreater(val, s) + isnormal(val) + signbit(val) + isgreater(val, s) +
        isless(val, s);
}

template<class T>
void test_poison() {
    // use these macros as proxies for determining C99 support:
#if defined(FP_ILOGB0) && defined(FP_INFINITE)
    //
    // These tests verify that our function overloads for Boost.Multiprecision
    // don't do anything nasty to the std:: overloads for built in types:
    //
    using namespace std;
    using namespace nil::crypto3::multiprecision;
    // using namespace boost::math;

    T a(2), b(0.3f), c(4), result(0);
    int i;

    result += abs(a);
    result += cosh(a);
    result += fmod(a, b);
    result += logb(a);
    result += remquo(a, b, &i);
    result += acos(b);
    result += erf(a);
    result += frexp(a, &i);
    result += lrint(a);
    result += rint(a);
    result += acosh(b);
    result += erfc(b);
    result += hypot(a, b);
    result += lround(c);
    result += round(c);
    result += asin(b);
    result += exp2(a);
    result += ilogb(b);
    result += modf(a, &b);
    result += scalbln(a, i);
    result += asinh(b);
    result += exp(b);
    result += ldexp(a, i);
    result += scalbn(a, i);
    result += atan(b);
    result += expm1(a);
    result += lgamma(a);
    result += sin(b);
    result += atan2(a, c);
    result += fabs(a);
    result += llrint(a);
    result += sinh(b);
    result += atanh(b);
    result += fdim(a, b);
    result += llround(a);
    result += nearbyint(a);
    result += sqrt(b);
    result += cbrt(a);
    result += floor(b);
    result += log(a);
    result += nextafter(a, b);
    result += tan(b);
    result += ceil(b);
    result += fma(a, b, c);
    result += log10(a);
    result += nexttoward(a, b);
    result += tanh(a);
    result += copysign(a, b);
    result += fmax(a, b);
    result += log1p(a);
    result += pow(a, b);
    result += tgamma(a);
    result += cos(b);
    result += fmin(a, b);
    result += log2(a);
    result += remainder(a, b);
    result += trunc(b);
    result += (min)(a, b);
    result += (max)(a, b);

#if !BOOST_WORKAROUND(BOOST_LIBSTDCXX_VERSION, < 60000)

    i = fpclassify(a) + isgreaterequal(a, b) + islessequal(a, b) + isnan(a) + isunordered(a, b) + isfinite(a) +
        isinf(a) + islessgreater(a, b) + isnormal(a) + signbit(a) + isgreater(a, b) + isless(a, b);
#endif
#endif
}

template<class T>
bool type_sets_errno(const T&) {
    return true;
}
#ifdef TEST_MPFR_50
template<unsigned Digits10,
         nil::crypto3::multiprecision::mpfr_allocation_type AllocateType,
         nil::crypto3::multiprecision::expression_template_option ExpressionTemplates>
bool type_sets_errno(
    const nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::mpfr_float_backend<Digits10, AllocateType>,
                                               ExpressionTemplates>&) {
    return false;
}
#endif
#ifdef TEST_FLOAT128
bool type_sets_errno(const nil::crypto3::multiprecision::float128&) {
    return false;
}
#endif

template<class T>
typename boost::enable_if_c<std::numeric_limits<T>::is_specialized>::type check_invalid(const T& val) {
    if (std::numeric_limits<T>::has_quiet_NaN) {
        BOOST_CHECK(isnan(val));
    } else {
        BOOST_CHECK_EQUAL(val, 0);
    }
    if (type_sets_errno(val))
        BOOST_CHECK_EQUAL(errno, EDOM);
    errno = 0;
}

template<class T>
typename boost::disable_if_c<std::numeric_limits<T>::is_specialized>::type check_invalid(const T& val) {
    check_invalid(static_cast<typename T::result_type>(val));
}

template<class T>
void check_erange(const T& val) {
    if (type_sets_errno(val))
        BOOST_CHECK_EQUAL(errno, ERANGE);
    errno = 0;
}

template<class T>
void test_c99_appendix_F() {
    //
    // Tests conformance to non-normative appendix F.9.1 of C99, basically how to handle
    // special cases, infinities and NaN's.
    //
    errno = 0;
    T tol = std::numeric_limits<T>::epsilon();
    // F.9.1.1:
    T arg = 1;
    T val = acos(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = 2;
    check_invalid(acos(arg));
    arg = -2;
    check_invalid(acos(arg));
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        check_invalid(acos(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(acos(arg));
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(acos(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(acos(arg));
    }
    // F.9.1.2:
    arg = 0;
    val = asin(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = asin(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    arg = 2;
    check_invalid(asin(arg));
    arg = -2;
    check_invalid(asin(arg));
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        check_invalid(asin(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(asin(arg));
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(asin(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(asin(arg));
    }
    // F.9.1.3:
    arg = 0;
    val = atan(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = atan(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = atan(arg);
        BOOST_CHECK_EQUAL(val, boost::math::constants::half_pi<T>());
        arg = -std::numeric_limits<T>::infinity();
        val = atan(arg);
        BOOST_CHECK_EQUAL(val, -boost::math::constants::half_pi<T>());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(asin(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(asin(arg));
    }
    // F.9.1.4:
    arg = 0;
    T arg2 = 0;
    val = atan2(arg, arg2);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    arg2 = -arg2;
    if (signbit(arg2)) {
        arg = 0;
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, boost::math::constants::pi<T>());
        BOOST_CHECK(signbit(val) == 0);
        arg = -arg;
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, -boost::math::constants::pi<T>());
        BOOST_CHECK(signbit(val));
    }
    arg = 0;
    arg2 = -2;
    val = atan2(arg, arg2);
    BOOST_CHECK_EQUAL(val, boost::math::constants::pi<T>());
    arg = -arg;
    if (signbit(arg)) {
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, -boost::math::constants::pi<T>());
    }
    arg = 0;
    arg2 = 2;
    val = atan2(arg, arg2);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    arg = -2;
    arg2 = 0;
    val = atan2(arg, arg2);
    BOOST_CHECK_EQUAL(val, -boost::math::constants::half_pi<T>());
    arg2 = -arg2;
    if (signbit(arg2)) {
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, -boost::math::constants::half_pi<T>());
    }
    arg = 2;
    arg2 = 0;
    val = atan2(arg, arg2);
    BOOST_CHECK_EQUAL(val, boost::math::constants::half_pi<T>());
    arg2 = -arg2;
    if (signbit(arg2)) {
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, boost::math::constants::half_pi<T>());
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = 2;
        arg2 = -std::numeric_limits<T>::infinity();
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, boost::math::constants::pi<T>());
        arg = -arg;
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, -boost::math::constants::pi<T>());
        arg = 2;
        arg2 = std::numeric_limits<T>::infinity();
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg = -arg;
        if (signbit(-T(0))) {
            val = atan2(arg, arg2);
            BOOST_CHECK_EQUAL(val, 0);
            BOOST_CHECK(signbit(val));
        }
        arg = std::numeric_limits<T>::infinity();
        arg2 = 2;
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, boost::math::constants::half_pi<T>());
        arg = -arg;
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, -boost::math::constants::half_pi<T>());
        arg = std::numeric_limits<T>::infinity();
        arg2 = -2;
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, boost::math::constants::half_pi<T>());
        arg = -arg;
        val = atan2(arg, arg2);
        BOOST_CHECK_EQUAL(val, -boost::math::constants::half_pi<T>());
        arg = std::numeric_limits<T>::infinity();
        arg2 = -std::numeric_limits<T>::infinity();
        val = atan2(arg, arg2);
        BOOST_CHECK_CLOSE_FRACTION(val, boost::math::constants::three_quarters_pi<T>(), tol);
        arg = -arg;
        val = atan2(arg, arg2);
        BOOST_CHECK_CLOSE_FRACTION(val, -boost::math::constants::three_quarters_pi<T>(), tol);
        arg = std::numeric_limits<T>::infinity();
        arg2 = std::numeric_limits<T>::infinity();
        val = atan2(arg, arg2);
        BOOST_CHECK_CLOSE_FRACTION(val, ldexp(boost::math::constants::pi<T>(), -2), tol);
        arg = -arg;
        val = atan2(arg, arg2);
        BOOST_CHECK_CLOSE_FRACTION(val, -ldexp(boost::math::constants::pi<T>(), -2), tol);
        if (std::numeric_limits<T>::has_quiet_NaN) {
            arg = std::numeric_limits<T>::quiet_NaN();
            arg2 = 2;
            check_invalid(atan2(arg, arg2));
            std::swap(arg, arg2);
            check_invalid(atan2(arg, arg2));
            arg = std::numeric_limits<T>::quiet_NaN();
            check_invalid(atan2(arg, arg2));
        }
    }
    // F.9.1.5:
    arg = 0;
    val = cos(arg);
    BOOST_CHECK_EQUAL(val, 1);
    arg = -arg;
    BOOST_CHECK_EQUAL(val, 1);
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        check_invalid(cos(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(cos(arg));
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(cos(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(cos(arg));
    }
    // F.9.1.6:
    arg = 0;
    val = sin(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = sin(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        check_invalid(sin(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(sin(arg));
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(sin(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(sin(arg));
    }
    // F.9.1.7:
    arg = 0;
    val = tan(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = tan(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        check_invalid(tan(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(tan(arg));
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(tan(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(tan(arg));
    }
    // F.9.2.1:
    arg = 1;
    val = acosh(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    check_invalid(acosh(arg));
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = acosh(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());

        arg = -std::numeric_limits<T>::infinity();
        check_invalid(acosh(arg));
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(acosh(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(acosh(arg));
    }
    // F.9.2.2:
    arg = 0;
    val = asinh(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = asinh(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = asinh(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());

        arg = -std::numeric_limits<T>::infinity();
        val = asinh(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(asinh(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(asinh(arg));
    }
    // F.9.2.3:
    arg = 0;
    val = atanh(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = atanh(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    arg = 2;
    check_invalid(atanh(arg));
    arg = -3;
    check_invalid(atanh(arg));

    if (std::numeric_limits<T>::has_infinity) {
        arg = 1;
        val = atanh(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        BOOST_CHECK(signbit(val) == 0);
        check_erange(val);
        arg = -arg;
        val = atanh(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
        BOOST_CHECK(signbit(val));
        check_erange(val);

        arg = std::numeric_limits<T>::infinity();
        check_invalid(atanh(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(atanh(arg));
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(atanh(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(atanh(arg));
    }
    // F.9.2.4:
    arg = 0;
    val = cosh(arg);
    BOOST_CHECK_EQUAL(val, 1);
    arg = -arg;
    if (signbit(arg)) {
        val = cosh(arg);
        BOOST_CHECK_EQUAL(val, 1);
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = (std::numeric_limits<T>::max)();
        val = cosh(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = cosh(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = std::numeric_limits<T>::infinity();
        val = cosh(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = cosh(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(cosh(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(cosh(arg));
    }
    // F.9.2.5:
    arg = 0;
    val = sinh(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = sinh(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = (std::numeric_limits<T>::max)();
        val = sinh(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = sinh(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
        arg = std::numeric_limits<T>::infinity();
        val = sinh(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = sinh(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(sinh(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(sinh(arg));
    }
    // F.9.2.6:
    arg = 0;
    val = tanh(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = tanh(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    arg = (std::numeric_limits<T>::max)();
    val = tanh(arg);
    BOOST_CHECK_EQUAL(val, 1);
    arg = -arg;
    val = tanh(arg);
    BOOST_CHECK_EQUAL(val, -1);
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = tanh(arg);
        BOOST_CHECK_EQUAL(val, 1);
        arg = -arg;
        val = tanh(arg);
        BOOST_CHECK_EQUAL(val, -1);
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(tanh(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(tanh(arg));
    }
    // F.9.3.1:
    arg = 0;
    val = exp(arg);
    BOOST_CHECK_EQUAL(val, 1);
    arg = -arg;
    if (signbit(arg)) {
        val = exp(arg);
        BOOST_CHECK_EQUAL(val, 1);
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = exp(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = exp(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg = (std::numeric_limits<T>::max)();
        val = exp(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = exp(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(exp(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(exp(arg));
    }
    // F.9.3.2:
    arg = 0;
    val = exp2(arg);
    BOOST_CHECK_EQUAL(val, 1);
    arg = -arg;
    if (signbit(arg)) {
        val = exp2(arg);
        BOOST_CHECK_EQUAL(val, 1);
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = exp2(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = exp2(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg = (std::numeric_limits<T>::max)();
        val = exp2(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = exp2(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(exp2(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(exp2(arg));
    }
    // F.9.3.3:
    arg = 0;
    val = expm1(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = expm1(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = expm1(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = expm1(arg);
        BOOST_CHECK_EQUAL(val, -1);
        arg = (std::numeric_limits<T>::max)();
        val = expm1(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = expm1(arg);
        BOOST_CHECK_EQUAL(val, -1);
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(expm1(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(expm1(arg));
    }
    // F.9.3.4:
    arg = 0;
    int ival;
    val = frexp(arg, &ival);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK_EQUAL(ival, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = frexp(arg, &ival);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = frexp(arg, &ival);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = frexp(arg, &ival);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        val = frexp(arg, &ival);
        BOOST_CHECK(isnan(val));
    }
    // F.9.3.5:
    typename T::backend_type::exponent_type eval;
    typename T::backend_type::exponent_type fp_ilogb0 =
        (std::numeric_limits<typename T::backend_type::exponent_type>::min)();
    typename T::backend_type::exponent_type fp_ilogbnan =
#ifdef FP_ILOGBNAN
        FP_ILOGBNAN < 0 ? (std::numeric_limits<typename T::backend_type::exponent_type>::min)() :
                          (std::numeric_limits<typename T::backend_type::exponent_type>::max)();
#else
        INT_MAX;
#endif

    arg = 0;
    eval = ilogb(arg);
    BOOST_CHECK_EQUAL(eval, fp_ilogb0);
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        eval = ilogb(arg);
        BOOST_CHECK_EQUAL(eval, (std::numeric_limits<typename T::backend_type::exponent_type>::max)());
        arg = -arg;
        eval = ilogb(arg);
        BOOST_CHECK_EQUAL(eval, (std::numeric_limits<typename T::backend_type::exponent_type>::max)());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        eval = ilogb(arg);
        BOOST_CHECK_EQUAL(eval, fp_ilogbnan);
    }
    // F.9.3.7:
    arg = 1;
    val = log(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    if (std::numeric_limits<T>::has_infinity) {
        arg = 0;
        val = log(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
        check_erange(val);
        arg = -arg;
        if (signbit(arg)) {
            val = log(arg);
            BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
            check_erange(val);
        }
        arg = -1;
        check_invalid(log(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(log(arg));
        arg = std::numeric_limits<T>::infinity();
        val = log(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(log(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(log(arg));
    }
    // F.9.3.8:
    arg = 1;
    val = log10(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    if (std::numeric_limits<T>::has_infinity) {
        arg = 0;
        val = log10(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
        check_erange(val);
        arg = -arg;
        if (signbit(arg)) {
            val = log10(arg);
            BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
            check_erange(val);
        }
        arg = -1;
        check_invalid(log10(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(log10(arg));
        arg = std::numeric_limits<T>::infinity();
        val = log10(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(log10(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(log10(arg));
    }
    // F.9.3.9:
    arg = 0;
    val = log1p(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = log1p(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = -1;
        val = log1p(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
        check_erange(val);
        arg = -2;
        check_invalid(log1p(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(log1p(arg));
        arg = std::numeric_limits<T>::infinity();
        val = log1p(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(log1p(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(log1p(arg));
    }
    // F.9.3.10:
    arg = 1;
    val = log2(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    if (std::numeric_limits<T>::has_infinity) {
        arg = 0;
        val = log2(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
        check_erange(val);
        arg = -arg;
        if (signbit(arg)) {
            val = log2(arg);
            BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
            check_erange(val);
        }
        arg = -1;
        check_invalid(log2(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(log2(arg));
        arg = std::numeric_limits<T>::infinity();
        val = log2(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(log2(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(log2(arg));
    }
    // F.9.3.11:
    if (std::numeric_limits<T>::has_infinity) {
        arg = 0;
        val = logb(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
        check_erange(val);
        arg = -arg;
        if (signbit(arg)) {
            val = logb(arg);
            BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
            check_erange(val);
        }
        arg = std::numeric_limits<T>::infinity();
        val = logb(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -std::numeric_limits<T>::infinity();
        val = logb(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(logb(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(logb(arg));
    }
    // F.9.3.13:
    arg = 0;
    val = scalbn(arg, 2);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = scalbn(arg, 2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = scalbn(arg, -100);
        BOOST_CHECK_EQUAL(val, arg);
        arg = -arg;
        val = scalbn(arg, -100);
        BOOST_CHECK_EQUAL(val, arg);
    }
    // F.9.4.1:
    arg = 0;
    val = cbrt(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = cbrt(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
#if !(defined(TEST_FLOAT128) && defined(BOOST_GCC_VERSION) && (BOOST_GCC_VERSION < 40800))
    //
    // This test fails with early implementations of libquadmath - not our issue!
    //
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = cbrt(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -std::numeric_limits<T>::infinity();
        val = cbrt(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
#endif
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(cbrt(arg));
        arg = -std::numeric_limits<T>::quiet_NaN();
        check_invalid(cbrt(arg));
    }
    // F.9.4.2:
    arg = 0;
    val = fabs(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = fabs(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = fabs(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -std::numeric_limits<T>::infinity();
        val = fabs(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    }
    // F.9.4.3:
    arg = 2;
    arg2 = 0;
    val = hypot(arg, arg2);
    BOOST_CHECK_EQUAL(val, arg);
    arg2 = -arg2;
    val = hypot(arg, arg2);
    BOOST_CHECK_EQUAL(val, arg);
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        arg2 = 2;
        val = hypot(arg, arg2);
        BOOST_CHECK_EQUAL(val, arg);
        arg = -arg;
        val = hypot(arg, arg2);
        BOOST_CHECK_EQUAL(val, -arg);
        arg2 = std::numeric_limits<T>::quiet_NaN();
        val = hypot(arg, arg2);
        BOOST_CHECK_EQUAL(val, -arg);
        arg = -arg;
        val = hypot(arg, arg2);
        BOOST_CHECK_EQUAL(val, arg);
    }
    // F.9.4.4:
    if (std::numeric_limits<T>::has_infinity) {
        arg = 0;
        arg2 = -3;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        check_erange(val);
        arg = -arg;
        if (signbit(arg)) {
            val = pow(arg, arg2);
            BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
            check_erange(val);
        }
        arg = 0;
        arg2 = -2;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        check_erange(val);
        arg = -arg;
        if (signbit(arg)) {
            val = pow(arg, arg2);
            BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
            check_erange(val);
        }
        arg = 0;
        arg2 = 3;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg = -arg;
        if (signbit(arg)) {
            val = pow(arg, arg2);
            BOOST_CHECK_EQUAL(val, 0);
            BOOST_CHECK(signbit(val));
        }
        arg = 0;
        arg2 = 2;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg = -arg;
        if (signbit(arg)) {
            val = pow(arg, arg2);
            BOOST_CHECK_EQUAL(val, 0);
            BOOST_CHECK(signbit(val) == 0);
        }
        arg = -1;
        arg2 = std::numeric_limits<T>::infinity();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg2 = -std::numeric_limits<T>::infinity();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg = 1;
        arg2 = 0;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg2 = std::numeric_limits<T>::infinity();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg2 = -std::numeric_limits<T>::infinity();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg2 = std::numeric_limits<T>::quiet_NaN();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg = 0;
        arg2 = 0;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg2 = -arg2;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg = std::numeric_limits<T>::infinity();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg2 = -arg2;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg = std::numeric_limits<T>::quiet_NaN();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
        arg2 = -arg2;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 1);
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = -2.5;
        arg2 = 2.5;
        check_invalid(pow(arg, arg2));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = 0.5;
        arg2 = -std::numeric_limits<T>::infinity();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -0.25;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = 2.5;
        arg2 = -std::numeric_limits<T>::infinity();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        arg = -arg;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        arg = 2.5;
        arg2 = std::numeric_limits<T>::infinity();
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -std::numeric_limits<T>::infinity();
        arg2 = -3;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        if (signbit(-T(0)))
            BOOST_CHECK(signbit(val));
        arg2 = -2;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg2 = -2.5;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg2 = 3;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
        arg2 = 2;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg2 = 2.5;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;    // +INF
        arg2 = -2;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg2 = -3;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg2 = -3.5;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg2 = 2;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg2 = 3;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg2 = 3.5;
        val = pow(arg, arg2);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    }
    // F.9.4.5:
    arg = 0;
    val = sqrt(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = sqrt(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
#if !(defined(TEST_FLOAT128) && defined(BOOST_GCC_VERSION) && (BOOST_GCC_VERSION < 40800))
    //
    // This test fails with early implementations of libquadmath - not our issue!
    //
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = sqrt(arg);
        BOOST_CHECK_EQUAL(val, arg);
        arg = -arg;
        check_invalid(sqrt(arg));
    }
#endif
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(sqrt(arg));
    }
    // F.9.5.1:
    arg = 0;
    val = erf(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = erf(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = erf(arg);
        BOOST_CHECK_EQUAL(val, 1);
        arg = -arg;
        val = erf(arg);
        BOOST_CHECK_EQUAL(val, -1);
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(erf(arg));
    }
    // F.9.5.2:
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = erfc(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val) == 0);
        arg = -arg;
        val = erfc(arg);
        BOOST_CHECK_EQUAL(val, 2);
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(erfc(arg));
    }
    // F.9.5.3:
    arg = 1;
    val = lgamma(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = 2;
    val = lgamma(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
#if !defined(BOOST_MPFR_VERSION) || (BOOST_MPFR_VERSION > 30103)
    arg = 0;
    val = lgamma(arg);
    BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    check_erange(val);
    arg = -1;
    val = lgamma(arg);
    BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    check_erange(val);
    arg = -2;
    val = lgamma(arg);
    BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    check_erange(val);
    arg = -std::numeric_limits<T>::infinity();
    val = lgamma(arg);
    BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
#endif
    arg = std::numeric_limits<T>::infinity();
    val = lgamma(arg);
    BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(lgamma(arg));
    }
    // F.9.5.4:
    if (std::numeric_limits<T>::has_infinity) {
        arg = 0;
        val = tgamma(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        check_erange(val);
        arg = -arg;
        if (signbit(arg)) {
            val = tgamma(arg);
            BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
            check_erange(val);
        }
        arg = -1;
        check_invalid(tgamma(arg));
        arg = -std::numeric_limits<T>::infinity();
        check_invalid(tgamma(arg));
        arg = std::numeric_limits<T>::infinity();
        val = tgamma(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(tgamma(arg));
    }
    // F.9.6.1:
    arg = 0;
    val = ceil(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = ceil(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = ceil(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = ceil(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(ceil(arg));
    }
    // F.9.6.2:
    arg = 0;
    val = floor(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = floor(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = floor(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = floor(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(floor(arg));
    }
    // F.9.6.3:
    arg = 0;
    val = nearbyint(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = nearbyint(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = nearbyint(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = nearbyint(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(nearbyint(arg));
    }
    // F.9.6.4:
    arg = 0;
    val = rint(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = rint(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = rint(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = rint(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(rint(arg));
    }
    // F.9.6.6:
    arg = 0;
    val = round(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = round(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = round(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = round(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(round(arg));
    }
    // F.9.6.8:
    arg = 0;
    val = trunc(arg);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = trunc(arg);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        val = trunc(arg);
        BOOST_CHECK_EQUAL(val, std::numeric_limits<T>::infinity());
        arg = -arg;
        val = trunc(arg);
        BOOST_CHECK_EQUAL(val, -std::numeric_limits<T>::infinity());
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        check_invalid(trunc(arg));
    }
    // F.9.7.1:
    arg = 0;
    arg2 = 2;
    val = fmod(arg, arg2);
    BOOST_CHECK_EQUAL(val, 0);
    BOOST_CHECK(signbit(val) == 0);
    arg = -arg;
    if (signbit(arg)) {
        val = fmod(arg, arg2);
        BOOST_CHECK_EQUAL(val, 0);
        BOOST_CHECK(signbit(val));
    }
    if (std::numeric_limits<T>::has_infinity) {
        arg = std::numeric_limits<T>::infinity();
        check_invalid(fmod(arg, arg2));
        arg = -arg;
        check_invalid(fmod(arg, arg2));
        arg = 2;
        arg2 = 0;
        check_invalid(fmod(arg, arg2));
        check_invalid(fmod(arg, -arg2));
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        arg = std::numeric_limits<T>::quiet_NaN();
        arg2 = 2;
        check_invalid(fmod(arg, arg2));
        swap(arg, arg2);
        check_invalid(fmod(arg, arg2));
        check_invalid(fmod(arg2, arg2));
    }
    //
    // Bugs:
    //
    int sign = 0;
    boost::math::lgamma(T(0.000001), &sign);
    BOOST_CHECK_EQUAL(sign, 1);
    sign = 0;
    boost::math::lgamma(T(0.5), &sign);
    BOOST_CHECK_EQUAL(sign, 1);
    sign = 0;
    boost::math::lgamma(T(0.9), &sign);
    BOOST_CHECK_EQUAL(sign, 1);
    sign = 0;
    boost::math::lgamma(T(1.1), &sign);
    BOOST_CHECK_EQUAL(sign, 1);
    sign = 0;
    boost::math::lgamma(T(1.9), &sign);
    BOOST_CHECK_EQUAL(sign, 1);
    sign = 0;
    boost::math::lgamma(T(2.1), &sign);
    BOOST_CHECK_EQUAL(sign, 1);
    sign = 0;
    boost::math::lgamma(T(20), &sign);
    BOOST_CHECK_EQUAL(sign, 1);
    sign = 0;
    boost::math::lgamma(T(-0.0000000000001), &sign);
    BOOST_CHECK_EQUAL(sign, -1);
    sign = 0;
    boost::math::lgamma(T(-0.5), &sign);
    BOOST_CHECK_EQUAL(sign, -1);
    sign = 0;
    boost::math::lgamma(T(-1.5), &sign);
    BOOST_CHECK_EQUAL(sign, 1);
    sign = 0;
    boost::math::lgamma(T(-2.5), &sign);
    BOOST_CHECK_EQUAL(sign, -1);
    sign = 0;
    boost::math::lgamma(T(-3.5), &sign);
    BOOST_CHECK_EQUAL(sign, 1);
}

template<class T>
void test_c99_appendix_F_tgammaq_addon_for_float128() {
#if defined(TEST_FLOAT128)
    // Special tests of tgamma for positive and negative
    // real values in the neighborhood of the origin.

    // At Wolfram Alpha: N[Gamma[1/3], 201]
    const T tgamma_third(
        "2."
        "67893853470774763365569294097467764412868937795730"
        "11009504283275904176101677438195409828890411887894"
        "19159049200072263335719084569504472259977713367708"
        "46976816728982305000321834255032224715694181755545");

    // Create a string such as "1e-84", where 84 exemplifies (digits10*5 + 5)/6
    // and the exponent varies depending on the value of digits10 for the type.
    // Consider, for instance digits10=100. In this case n_exp=(100*5 + 5)/6 = 84.

    const int n_exp = (std::max)(((std::numeric_limits<T>::digits10 * 5) + 5) / 6, 1);

    const T tgamma_tol("1e-" + boost::lexical_cast<std::string>(n_exp));

    // Check tgamma(1/3 - n) for n in 0...24.
    for (int n = 0; n < 25; ++n) {
        const T tgamma_val = tgamma(T((T(1) / T(3)) - T(n)));

        T tgamma_control(tgamma_third);

        for (unsigned int k = 0U; k < static_cast<unsigned int>(n); ++k) {
            tgamma_control *= -3;
            tgamma_control /= ((3U * (k + 1U)) - 1U);
        }

        BOOST_CHECK_CLOSE_FRACTION(tgamma_val, tgamma_control, tgamma_tol);
    }

    // Check tgamma(1/3 + n) for n in 0...24.
    for (unsigned int n = 0U; n < 25U; ++n) {
        const T tgamma_val = tgamma(T((T(1) / T(3)) + T(n)));

        T tgamma_control(tgamma_third);

        for (unsigned int k = 0U; k < n; ++k) {
            tgamma_control /= 3;
            tgamma_control *= ((3U * (k + 1U)) - 2U);
        }

        BOOST_CHECK_CLOSE_FRACTION(tgamma_val, tgamma_control, tgamma_tol);
    }
#endif
}

int main() {
    test_poison<float>();
    test_poison<double>();
#ifdef TEST_MPF_50
    test<nil::crypto3::multiprecision::mpf_float_50>();
    test<nil::crypto3::multiprecision::mpf_float_100>();
#endif
#ifdef TEST_MPFR_50
    std::cout << "Testing MPFR: " << MPFR_VERSION_STRING << std::endl;
    test<nil::crypto3::multiprecision::mpfr_float_50>();
    test<nil::crypto3::multiprecision::mpfr_float_100>();
    test_c99_appendix_F<nil::crypto3::multiprecision::mpfr_float_50>();
#endif
#ifdef TEST_MPFI_50
    test<nil::crypto3::multiprecision::mpfi_float_50>();
    test<nil::crypto3::multiprecision::mpfi_float_100>();
#endif
#ifdef TEST_CPP_DEC_FLOAT
    test<nil::crypto3::multiprecision::cpp_dec_float_50>();
#if !(defined(__GNUC__) && defined(_WIN32))    // Object file too large otherwise
    test<nil::crypto3::multiprecision::cpp_dec_float_100>();
#endif
    test_c99_appendix_F<nil::crypto3::multiprecision::cpp_dec_float_50>();
#endif
#ifdef TEST_CPP_DEC_FLOAT_2
    // Some "peculiar" digit counts which stress our code:
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<65>>>();
#if !(defined(__GNUC__) && defined(_WIN32))    // Object file too large otherwise
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<64>>>();
#endif
#endif
#ifdef TEST_CPP_DEC_FLOAT_3
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<63>>>();
#if !(defined(__GNUC__) && defined(_WIN32))    // Object file too large otherwise
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<62>>>();
#endif
#endif
#ifdef TEST_CPP_DEC_FLOAT_4
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<61, long long>>>();
#if !(defined(__GNUC__) && defined(_WIN32))    // Object file too large otherwise
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<60, long long>>>();
#endif
#endif
#ifdef TEST_CPP_DEC_FLOAT_5
    test<nil::crypto3::multiprecision::number<
        nil::crypto3::multiprecision::cpp_dec_float<59, long long, std::allocator<char>>>>();
#endif
#ifdef TEST_CPP_DEC_FLOAT_6
    test<nil::crypto3::multiprecision::number<
        nil::crypto3::multiprecision::cpp_dec_float<58, long long, std::allocator<char>>>>();
#endif
#ifdef TEST_CPP_BIN_FLOAT
    test<nil::crypto3::multiprecision::cpp_bin_float_50>();
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_bin_float<100>,
                                              nil::crypto3::multiprecision::et_on>>();
#endif
#ifdef TEST_CPP_BIN_FLOAT_2
    test<nil::crypto3::multiprecision::number<
        nil::crypto3::multiprecision::cpp_bin_float<35,
                                                    nil::crypto3::multiprecision::digit_base_10,
                                                    std::allocator<char>,
                                                    boost::long_long_type>>>();
#endif
#ifdef TEST_CPP_BIN_FLOAT_3
    test_c99_appendix_F<nil::crypto3::multiprecision::cpp_bin_float_50>();
    test_c99_appendix_F<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_bin_float<100>,
                                                             nil::crypto3::multiprecision::et_on>>();
#endif
#ifdef TEST_FLOAT128
    test<nil::crypto3::multiprecision::float128>();
    test_c99_appendix_F<nil::crypto3::multiprecision::float128>();
    test_c99_appendix_F_tgammaq_addon_for_float128<nil::crypto3::multiprecision::float128>();
#endif

    return boost::report_errors();
}
